package cn.snmp.mutual.uk.stack;

import java.util.*;

import cn.snmp.mutual.uk.util.SnmpUtilities;

import java.io.*;

class AsnEncoderv3 extends AsnEncoderBase {
    private static final String version_id = "@(#)$Id: AsnEncoderv3.java,v 3.5 2009/03/05 12:48:59 birgita Exp $ Copyright Westhawk Ltd";
    static byte[] dummyFingerPrint;

    byte[] EncodeSNMPv3(final SnmpContextv3Basis context, final int contextMsgId, final TimeWindowNode node, final byte msg_type, final int pduId, final int errstat, final int errind, final Enumeration ve) throws IOException, EncodingException {
        final AsnSequence asnTopSeq = new AsnSequence();
        final AsnSequence asnHeaderData = new AsnSequence();
        asnHeaderData.add(new AsnInteger(contextMsgId));
        asnHeaderData.add(new AsnInteger(context.getMaxRecvSize()));
        asnHeaderData.add(new AsnOctets(this.getMsgFlags(context, msg_type)));
        asnHeaderData.add(new AsnInteger(3));
        final AsnSequence asnPlainScopedPdu = new AsnSequence();
        asnPlainScopedPdu.add(new AsnOctets(context.getContextEngineId()));
        asnPlainScopedPdu.add(new AsnOctets(context.getContextName()));
        final AsnObject asnPduObject = this.EncodePdu(msg_type, pduId, errstat, errind, ve);
        asnPlainScopedPdu.add(asnPduObject);
        if (AsnObject.debug > 10) {
            System.out.println("\nEncode USM: node " + node.toString());
        }
        final AsnSequence asnSecurityObject = new AsnSequence();
        final byte[] engineIdBytes = SnmpUtilities.toBytes(node.getSnmpEngineId());
        asnSecurityObject.add(new AsnOctets(engineIdBytes));
        asnSecurityObject.add(new AsnInteger(node.getSnmpEngineBoots()));
        asnSecurityObject.add(new AsnInteger(node.getSnmpEngineTime()));
        asnSecurityObject.add(new AsnOctets(context.getUserName()));
        AsnOctets fingerPrintOct;
        if (context.isUseAuthentication()) {
            fingerPrintOct = new AsnOctets(AsnEncoderv3.dummyFingerPrint);
        } else {
            fingerPrintOct = new AsnOctets("");
        }
        asnSecurityObject.add(fingerPrintOct);
        AsnOctets asnEncryptedScopedPdu = null;
        AsnOctets privOct;
        if (context.isUsePrivacy()) {
            byte[] privKey = null;
            final int aprot = context.getAuthenticationProtocol();
            if (aprot == 0) {
                final byte[] passwKey = context.getPrivacyPasswordKeyMD5();
                privKey = SnmpUtilities.getLocalizedKeyMD5(passwKey, node.getSnmpEngineId());
            } else {
                final byte[] passwKey = context.getPrivacyPasswordKeySHA1();
                privKey = SnmpUtilities.getLocalizedKeySHA1(passwKey, node.getSnmpEngineId());
            }
            final int pprot = context.getPrivacyProtocol();
            byte[] salt = null;
            if (pprot == 3) {
                salt = SnmpUtilities.getSaltAES();
            } else {
                salt = SnmpUtilities.getSaltDES(node.getSnmpEngineBoots());
            }
            privOct = new AsnOctets(salt);
            final ByteArrayOutputStream bout = new ByteArrayOutputStream();
            asnPlainScopedPdu.write(bout);
            final byte[] plaintext = bout.toByteArray();
            byte[] encryptedText = null;
            if (pprot == 3) {
                encryptedText = SnmpUtilities.AESencrypt(plaintext, privKey, node.getSnmpEngineBoots(), node.getSnmpEngineTime(), salt);
            } else {
                encryptedText = SnmpUtilities.DESencrypt(plaintext, privKey, salt);
            }
            asnEncryptedScopedPdu = new AsnOctets(encryptedText);
            if (AsnObject.debug > 10) {
                System.out.println("Encrypted body  with " + SnmpContextv3Basis.ProtocolNames[pprot]);
            }
        } else {
            privOct = new AsnOctets("");
        }
        asnSecurityObject.add(privOct);
        final ByteArrayOutputStream secOut = new ByteArrayOutputStream();
        asnSecurityObject.write(secOut);
        final byte[] bytes = secOut.toByteArray();
        final AsnOctets asnSecurityParameters = new AsnOctets(bytes);
        asnTopSeq.add(new AsnInteger(3));
        asnTopSeq.add(asnHeaderData);
        asnTopSeq.add(asnSecurityParameters);
        if (context.isUsePrivacy()) {
            asnTopSeq.add(asnEncryptedScopedPdu);
        } else {
            asnTopSeq.add(asnPlainScopedPdu);
        }
        if (AsnObject.debug > 10) {
            System.out.println("\n" + this.getClass().getName() + ".EncodeSNMPv3(): ");
        }
        final ByteArrayOutputStream bout = new ByteArrayOutputStream();
        asnTopSeq.write(bout);
        final int sz = bout.size();
        if (sz > context.getMaxRecvSize()) {
            throw new EncodingException("Packet size (" + sz + ") is > maximum size (" + context.getMaxRecvSize() + ")");
        }
        final byte[] message = bout.toByteArray();
        if (context.isUseAuthentication()) {
            byte[] calcFingerPrint = null;
            final int prot = context.getAuthenticationProtocol();
            if (prot == 0) {
                final byte[] passwKey2 = context.getAuthenticationPasswordKeyMD5();
                final byte[] authkey = SnmpUtilities.getLocalizedKeyMD5(passwKey2, node.getSnmpEngineId());
                calcFingerPrint = SnmpUtilities.getFingerPrintMD5(authkey, message);
            } else {
                final byte[] passwKey2 = context.getAuthenticationPasswordKeySHA1();
                final byte[] authkey = SnmpUtilities.getLocalizedKeySHA1(passwKey2, node.getSnmpEngineId());
                calcFingerPrint = SnmpUtilities.getFingerPrintSHA1(authkey, message);
            }
            final int usmPos = asnSecurityParameters.getContentsPos();
            int fpPos = fingerPrintOct.getContentsPos();
            fpPos += usmPos;
            if (AsnObject.debug > 10) {
                final int fpLength = fingerPrintOct.getContentsLength();
                final String str = "Pos finger print = " + fpPos + ", len = " + fpLength;
                SnmpUtilities.dumpBytes(str, calcFingerPrint);
            }
            System.arraycopy(calcFingerPrint, 0, message, fpPos, AsnEncoderv3.dummyFingerPrint.length);
        }
        return message;
    }

    private byte[] getMsgFlags(final SnmpContextv3Basis context, final byte msg_type) throws EncodingException {
        byte authMask = 0;
        if (context.isUseAuthentication()) {
            authMask = 1;
        }
        byte privMask = 0;
        if (context.isUsePrivacy()) {
            if (!context.isUseAuthentication()) {
                throw new EncodingException("Encryption without authentication is not allowed");
            }
            privMask = 2;
        }
        byte reportMask = 0;
        if (!context.isAuthoritative(msg_type)) {
            reportMask = 4;
        }
        final byte[] msgFlags = {(byte) (authMask | privMask | reportMask)};
        return msgFlags;
    }

    static {
        AsnEncoderv3.dummyFingerPrint = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    }
}
